package org.wh1tew0lf.vframe.framework;

import java.util.Date;
import java.util.Map;

import org.wh1tew0lf.vframe.framework.vframeexception.VDatabaseRestrictionValueIsNullException;
import org.wh1tew0lf.vframe.framework.vframeexception.VDatabaseRestrictionValueTypeNotSupportedException;
import org.wh1tew0lf.vframe.framework.vframeexception.VFrameException;

public final class VDatabaseValue implements IDatabaseRestriction {

	public static final int VALUE_TYPE_NULL = 0x0;
	public static final int VALUE_TYPE_NUMBER = 0x1;
	public static final int VALUE_TYPE_STRING = 0x2;
	public static final int VALUE_TYPE_FIELD = 0x3;
	public static final int VALUE_TYPE_FUNC = 0x4;

	private VDatabaseField fieldValue = null;
	private Number numberValue = null;
	private String textValue = null;
	private VDatabaseValueOperator operator = VDatabaseValueOperator.NULL;
	private VDatabaseValue[] dbValues = null;
	private int type = VALUE_TYPE_NULL;

	public static VDatabaseValue string(String value) {
		return new VDatabaseValue(value);
	}

	public static VDatabaseValue number(Number value) {
		return new VDatabaseValue(value);
	}

	public static VDatabaseValue date(Date value) {
		return new VDatabaseValue(value);
	}

	public static VDatabaseValue field(VDatabaseField value) {
		return new VDatabaseValue(value);
	}

	public static VDatabaseValue field(String value) throws VFrameException {
		return new VDatabaseValue(VDatabaseField.field(value));
	}
	
	public static VDatabaseValue min(String fieldName) throws VFrameException {
		return VDatabaseValue.valuesWithOperator(
				VDatabaseValueOperator.MIN, VDatabaseValue.field(fieldName));
	}
	
	public static VDatabaseValue max(String fieldName) throws VFrameException {
		return VDatabaseValue.valuesWithOperator(
				VDatabaseValueOperator.MAX, VDatabaseValue.field(fieldName));
	}
	
	public static VDatabaseValue average(String fieldName) throws VFrameException {
		return VDatabaseValue.valuesWithOperator(
				VDatabaseValueOperator.AVG, VDatabaseValue.field(fieldName));
	}
	
	public static VDatabaseValue count(String fieldName) throws VFrameException {
		return VDatabaseValue.valuesWithOperator(
				VDatabaseValueOperator.COUNT, VDatabaseValue.field(fieldName));
	}

	public static String oracleTextWithIn(String text, String section)
			throws VFrameException {
		return VDatabaseValue.valuesWithOperator(
				VDatabaseValueOperator.ORACLE_TEXT_WITHIN, section, text)
				.toString(false, null);
	}

	protected static VDatabaseValue valuesWithOperator(
			VDatabaseValueOperator operator, VDatabaseValue... databaseValues) {
		return new VDatabaseValue(operator, databaseValues);
	}

	protected static VDatabaseValue valuesWithOperator(
			VDatabaseValueOperator operator, Object... databaseValueObjects)
			throws VFrameException {
		if (databaseValueObjects == null) {
			throw new VDatabaseRestrictionValueIsNullException();
		}
		int valueCount = databaseValueObjects.length;
		VDatabaseValue values[] = new VDatabaseValue[valueCount];
		for (int i = 0; i < valueCount; i++) {
			Object currentValue = databaseValueObjects[i];
			Class<?> valueClass = currentValue.getClass();
			if (currentValue instanceof VDatabaseValue) {
				values[i] = (VDatabaseValue) currentValue;
			} else if (valueClass == short.class || valueClass == int.class
					|| valueClass == long.class || valueClass == double.class
					|| valueClass == Integer.class
					|| valueClass == Number.class) {
				values[i] = VDatabaseValue.number((Number) currentValue);
			} else if (valueClass == String.class) {
				values[i] = VDatabaseValue.string((String) currentValue);
			} else if (valueClass == Date.class) {
				values[i] = VDatabaseValue.date((Date) currentValue);
			} else if (valueClass == VDatabaseField.class) {
				values[i] = VDatabaseValue.field((VDatabaseField) currentValue);
			} else {
				throw new VDatabaseRestrictionValueTypeNotSupportedException(
						currentValue.getClass().getName());
			}
		}
		return new VDatabaseValue(operator, values);
	}

	private VDatabaseValue(VDatabaseValueOperator operator,
			VDatabaseValue... databaseValues) {
		this.operator = (operator == null) ? VDatabaseValueOperator.NULL
				: operator;
		if (databaseValues == null) {
			this.type = VDatabaseValue.VALUE_TYPE_NULL;
		} else {
			this.dbValues = databaseValues;
			this.type = VDatabaseValue.VALUE_TYPE_FUNC;
		}
	}

	private VDatabaseValue(VDatabaseField value) {
		if (value == null) {
			this.type = VDatabaseValue.VALUE_TYPE_NULL;
		} else {
			this.fieldValue = value;
			this.type = VDatabaseValue.VALUE_TYPE_FIELD;
			this.operator = VDatabaseValueOperator.FIELD;
		}
	}

	private VDatabaseValue(String value) {
		if (value == null) {
			this.type = VDatabaseValue.VALUE_TYPE_NULL;
		} else {
			this.textValue = value.replace("'", "''");
			this.textValue = this.textValue.replace("&", "\\&");
			this.type = VDatabaseValue.VALUE_TYPE_STRING;
			this.operator = VDatabaseValueOperator.STRING;
		}
	}

	private VDatabaseValue(Number value) {
		this.numberValue = value;
		this.type = VDatabaseValue.VALUE_TYPE_NUMBER;
	}

	private VDatabaseValue(Date value) {
		long longValue = -1;
		if (value != null) {
			longValue = value.getTime();
		}
		this.numberValue = longValue;
		this.type = VDatabaseValue.VALUE_TYPE_NUMBER;
	}

	public boolean isNull() {
		return this.type == VDatabaseValue.VALUE_TYPE_NULL;
	}

	public boolean isStringValue() {
		return this.type == VDatabaseValue.VALUE_TYPE_STRING;
	}

	public boolean isFieldValue() {
		return this.type == VDatabaseValue.VALUE_TYPE_FIELD;
	}

	public boolean isFunctionValue() {
		return this.type == VDatabaseValue.VALUE_TYPE_FUNC;
	}

	public Number getNumberValue() {
		return numberValue;
	}

	public String getTextValue() {
		return textValue;
	}

	public VDatabaseValueOperator getOperator() {
		return operator;
	}

	protected boolean isEqualTo(VDatabaseValue another, boolean caseSensitive) {
		if (another == null) {
			return false;
		}
		if (this.type != another.type) {
			return false;
		}
		if (this.operator != another.operator) {
			return false;
		}
		if (this.isFunctionValue() && another.isFunctionValue()) {
			if (this.dbValues.length != another.dbValues.length) {
				return false;
			}
			for (int i = 0; i < this.dbValues.length; i++) {
				if (!(this.dbValues[i].isEqualTo(another.dbValues[i],
						caseSensitive))) {
					return false;
				}
			}
			return true;
		} else if (this.isNull() && another.isNull()) {
			return true;
		} else if (this.isStringValue() && another.isStringValue()) {
			return ((this.textValue != null) && (caseSensitive ? this.textValue
					.equals(another.textValue) : this.textValue
					.equalsIgnoreCase(another.textValue)));
		} else if (this.isFieldValue() && another.isFieldValue()) {
			return this.fieldValue.isEqualTo(another.fieldValue);
		} else {
			return ((this.numberValue != null) && (this.numberValue
					.equals(another.numberValue)));
		}
	}

	@Override
	public String toString(boolean rawValueData, Map<String, String> nameMap)
			throws VFrameException {
		if (this.type == VALUE_TYPE_FUNC)
			return this.operator
					.toString(rawValueData, null, nameMap, dbValues);
		else if (this.type == VALUE_TYPE_STRING) {
			return this.operator.toString(rawValueData, textValue);
		} else if (this.type == VALUE_TYPE_NULL) {
			return this.operator.toString(rawValueData, "NULL");
		} else if (this.type == VALUE_TYPE_FIELD) {
			return this.operator.toString(rawValueData,
					this.fieldValue.toString(rawValueData, nameMap), nameMap);
		} else {
			return this.operator.toString(rawValueData,
					this.numberValue.toString());
		}
	}
}
